﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Sims3Package.Interface;

namespace Sims3Package
{
    public class ResourceEntry :  ITGIblock, IPackageIndex, IDisposable, IResourceEntry
    {
        // Public Property
        /// <summary>
        /// The instance of entry
        /// </summary>
        public ulong ResourceInstance { get { return ((ulong)InstanceHi) << 32 | InstanceLo; } set { this.InstanceHi = (uint)(value >> 32); this.InstanceLo = (uint)(value & 0x00000000FFFFFFFF); } }
        /// <summary>
        /// The type of entry
        /// </summary>
        public uint ResourceType { get; set; }
        /// <summary>
        /// The group of entry
        /// </summary>
        public uint ResourceGroup { get; set; }
        /// <summary>
        /// File size of entry
        /// </summary>
        public Int32 FileSize { get { return this.mFilesize; } }
        /// <summary>
        /// Memory size of entry
        /// </summary>
        public Int32 Memsize { get { return mMemsize; } }
        /// <summary>
        /// Whether entry data is compressed
        /// </summary>
        public bool Compressed
        {
            get { return this.mCompressed; }
            set
            {

                this.mCompressed = value;

            }
        }
        // Private Property
        /// <summary>
        /// Absolute position of chuck data
        /// </summary>
        public Int32 Chunkoffset { get; set; }
        /// <summary>
        /// Unknown 1
        /// </summary>
        public byte Unknown1 { get; set; }
        /// <summary>
        /// Unknown 2 
        /// </summary>
        public UInt16 Unknown2 { get; set; }
        /// <summary>
        /// TGI Order used in paring for wrapper
        /// </summary>
        public TGIOrder Order { get; set; }
        protected internal Stream ResourceStream 
        { 
            get { return this.mResourceStream; } 
            set 
            {
                this.mResourceStream = value;
                if (value == null) return;
                this.mResourceStream.Position = 0;
                this.mMemsize = value != null ? (int)value.Length : 0;
                this.Chunkoffset = Int32.MaxValue;
                
                this.mFilesize = this.mMemsize;
            } 
        }
        private Int32 mMemsize;
        protected internal Int32 mFilesize;
        protected internal bool mCompressed;
        private Stream mResourceStream;
        protected internal uint InstanceLo;
        protected internal uint InstanceHi;
        private bool Disposed = false;
        protected internal Int32 realsize;

        
        // Constructors
        /// <summary>
        /// Initialize a resource entry with given TGI, in "TGI" order
        /// </summary>
        /// <param name="resourcetype">uint of resource type</param>
        /// <param name="resourcegroup">uint of resource group</param>
        /// <param name="resourceinstance">ulong of resource instance</param>
        public ResourceEntry(uint resourcetype, uint resourcegroup, ulong resourceinstance)
            : this(resourcetype, resourcegroup, resourceinstance, TGIOrder.TGI)
        { }
        
        /// <summary>
        /// Initialize a resource entry with given TGI order and a null entry stream in compression
        /// </summary>
        /// <param name="s">Stream that contains TGI information</param>
        /// <param name="order">The sequence of TGI order. e.g. TGI or IGT</param>
        public ResourceEntry(Stream s, TGIOrder order)
        {
            this.Order = order;
            BinaryReader r = new BinaryReader(s);
            byte[] byteorder = BitConverter.GetBytes((int)order);
            for (int i = 0; i < 3; i++)
            {
                switch (byteorder[i])
                {
                    case 0x00:
                        this.ResourceType = r.ReadUInt32();
                        break;
                    case 0x01:
                        this.ResourceType = r.ReadUInt32();
                        break;
                    case 0x02:
                        this.ResourceInstance = r.ReadUInt64();
                        break;
                }
            }
        }
        
        /// <summary>
        /// Initialize a resource entry with given TGI and a null entry stream in compression
        /// </summary>
        /// <param name="resourcetype">uint of resource type</param>
        /// <param name="resourcegroup">uint of resource group</param>
        /// <param name="resourceinstance">ulong of resource instance</param>
        /// <param name="order">The sequence of TGI order. e.g. TGI or IGT</param>
        public ResourceEntry(uint resourcetype, uint resourcegroup, ulong resourceinstance, TGIOrder order)
            : this(resourcetype, resourcegroup, resourceinstance, TGIOrder.TGI, true)
        { }
        /// <summary>
        /// Initialize a resource entry with given TGI and a null entry stream in compression
        /// </summary>
        /// <param name="resourcetype">uint of resource type</param>
        /// <param name="resourcegroup">uint of resource group</param>
        /// <param name="resourceinstance">ulong of resource instance</param>
        /// <param name="order">The sequence of TGI order. e.g. TGI or IGT</param>
        /// <param name="compress">Whether to compress or not</param>
        public ResourceEntry(uint resourcetype, uint resourcegroup, ulong resourceinstance, TGIOrder order, bool compress)
            : this(resourcetype, resourcegroup, resourceinstance, order, compress, null)
        { }

        public ResourceEntry(uint resourcetype, uint resourcegroup, ulong resourceinstance, TGIOrder order, bool compress, Stream s)
        {
            this.ResourceType = resourcetype;
            this.ResourceGroup = resourcegroup;
            this.ResourceInstance = resourceinstance;
            this.Order = order;
            this.mCompressed = compress;
            this.ResourceStream = s;
            this.Unknown2 = 1;
            this.Chunkoffset = int.MaxValue;

        }
        /// <summary>
        /// Initialize a resource entry with given TGI and a null entry stream in compression
        /// </summary>
        /// <param name="block">TGI block</param>
        public ResourceEntry(ITGIblock block)
            : this(block, null)
        { }
        /// <summary>
        /// Initialize a resource entry with given TGI and a stream in compression
        /// </summary>
        /// <param name="block">TGI block</param>
        /// <param name="s">Resource stream</param>
        public ResourceEntry(ITGIblock block, Stream s)
            : this(block, s, true)
        { }
        /// <summary>
        /// Initialize a resource entry with given TGI and a stream with compression whether or not
        /// </summary>
        /// <param name="block">TGI block</param>
        /// <param name="s">Resource stream</param>
        /// <param name="compress">whether to compress stream or not</param>
        public ResourceEntry(ITGIblock block, Stream s, bool compress)
            : this(block.ResourceType, block.ResourceGroup, block.ResourceInstance, block.Order, compress, s)
        { }

        /// <summary>
        /// Create a new expanded index entry from the header and entry data passed
        /// </summary>
        /// <param name="header">header ints (same for each index entry); [0] is the index type</param>
        /// <param name="entry">entry ints (specific to this entry)</param>
        protected internal ResourceEntry(Int32[] header, Int32[] entry)
        {
            Int32[] dataInt = new Int32[header.Length + entry.Length - 1];

            // Merge two int array to one
            int type = header[0];
            int countEntry = 0;
            for (int i = 0; i < 8; i++)
                dataInt[i] = ((type >> i) | 1) != (type >> i) ? dataInt[i] = entry[countEntry++] : dataInt[i] = header[i - countEntry + 1];
                
            

            byte[] Data = new byte[dataInt.Length * sizeof(Int32)];
            Buffer.BlockCopy(dataInt, 0, Data, 0, Data.Length);



            this.ResourceType = BitConverter.ToUInt32(Data, 0);
            this.ResourceGroup = BitConverter.ToUInt32(Data, 4);
            this.InstanceHi = BitConverter.ToUInt32(Data, 8);
            this.InstanceLo = BitConverter.ToUInt32(Data, 12);
            this.Chunkoffset = BitConverter.ToInt32(Data, 16);
            Int32 filesize = BitConverter.ToInt32(Data, 20);
            this.Unknown1 = (byte)(filesize >> 31);
            this.mFilesize = (filesize << 1) >> 1;
            this.mMemsize = BitConverter.ToInt32(Data, 24);
            UInt16 compressed = BitConverter.ToUInt16(Data, 28);
            this.mCompressed = (compressed == 0xFFFF);
            this.Unknown2 = BitConverter.ToUInt16(Data, 30);
        }

        /// <summary>
        /// Get index information in bytes
        /// </summary>
        /// <param name="type">Header type</param>
        /// <returns>bytes containing index information</returns>
        public Byte[] GetIndexBytes(Int32 type)
        {
            using (MemoryStream s = new MemoryStream())
            {
                BinaryWriter w = new BinaryWriter(s);
                if ((type | 1) != type) w.Write(this.ResourceType);
                if ((type | 1 << 1) != type) w.Write(this.ResourceGroup);
                if ((type | 1 << 2) != type) w.Write(this.InstanceHi);
                if ((type | 1 << 3) != type) w.Write(this.InstanceLo);
                w.Write(this.Chunkoffset);
                w.Write((int)(this.mFilesize | ((Int32)(Unknown1)) << 31));
                w.Write(this.mMemsize);
                w.Write((int)((this.mCompressed ? 0xFFFF : 0x0000) | (int)(Unknown2) << 16));
                return s.ToArray();
            }
        }
        /// <summary>
        /// Release all resources used by ResourceEntry
        /// </summary>
        public void Dispose()
        {
            if(!this.Disposed)
                Dispose(true);
            
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.mResourceStream != null)
                    this.mResourceStream.Dispose();
            }
            this.Disposed = true;
        }

    }

    /// <summary>
    /// TGI order NOTE: This can be shorten to 9 bits i.e. short
    /// </summary>
    public enum TGIOrder : int
    {
        TGI = 0x000102,
        TIG = 0x000201,
        IGT = 0x020100,
        ITG = 0x020001,
        GIT = 0x010200,
        GTI = 0x010002
    }
}
